'''
Created on Jun 23, 2014

@author: Brian
'''


""" Giovanni's MicroCompiler Demo

<program>       ::=     { <id> ; <vars > <stmtlist>  }
<vars>          ::=     V { <id> ; }                       % DECLARATIONS

<stmtlist>      ::=     <stmt> { ; <stmt> }
<stmt>          ::=     P <id>  |  <id> = <expr>      

<expr>          ::=     <factor> {  (+ | -)   <factor> }   % No precedence
<factor>                ::=  (+|-)   ID | Number                                                             

"""

import sys
import shlex

#===============================================================================
# # Lexical Analyser - the "Scanner" - Define the TokenTypes
#===============================================================================

tokenNames =\
  [ "unknownToken","programsym", "idsym", "assignsym", "leftbrace", "rightbrace","comma",
    "varsym","functionsym", "semicolon", "whilesym", "leftbracket", "rightbracket",
    "printsym","printmsg", "ifsym", "elsesym","declaresym","forsym", "equals", "notequals", "lessthan", "greaterthan",
    "readch", "readint", "writech", "writeint","addup","addup_1",
    "number", "plus", "minus", "mpy", "div", "jumpne", "jumpeq","lessthanequal","greaterthanequal","ex","remainder","returnvalue","return_a", "endfile"]

# define the tokens as variables
unknownToken,programsym, idsym, assignsym, leftbrace, rightbrace, comma,\
    varsym,functionsym, semicolon, whilesym,  leftbracket, rightbracket,\
    printsym,printmsg, ifsym, elsesym,declaresym, forsym, equals, notequals, lessthan, greaterthan,\
    readch, readint, writech, writeint, addup,addup_1,\
    number, plus, minus, mpy, div, jumpne, jumpeq, lessthanequal, greaterthanequal,ex,remainder,returnvalue,return_a, endfile = range(0, len(tokenNames))
              
#===============================================================================
class symbol:
#===============================================================================
    name      = None   # String representation
    token     = None;  # Corresponding token
    address   = 0;     # For variables, their runtime address
    value     = None   # on defined for numbers

    def __init__(self, name, token, value = 0):
        self.name    = name
        self.token   = token
        self.address = 0   # for identifiers, their address
        self.value   = value   # for numbers, their value

              
symtbl = {}; # The Symbol Table, a dictionary of symbols indexed by name

#======================================================================
# GLOBALS for output of Lexical Analyser
# Set by each call to GeToken()

token = None;   # A symbol

line      = "\n" # the complex source file as a string
charIndex = 0    # the position in the source

linenumber= 0    # The current  number

EOF = chr(0)
#======================================================================
# Run Time Code Generation Variables

varptr  = 500   # Arbitrary base address of memory for variables
codeptr = 10    # Address to output next instruction (for code)


#===============================================================================
#                    Symbol Table Management Routines
#===============================================================================

#===============================================================================
def addToSymTbl (name, token):
#===============================================================================
    symtbl[name] = symbol(name, token)  # Add a new symbol to the dictionary
    return symtbl[name]


#===============================================================================
def lookup (thisName): 
#===============================================================================
    "# Find a given identifier in the Symbol Table, Return the symtbl entry"
    if symtbl.has_key(thisName):  # thisName has been seen before
        return symtbl[thisName]   # return the symtbl entry
    else: return None


#===============================================================================
def initSymTbl():
#===============================================================================
    "# Initialise Symbol Table, and preload reserved words"
    addToSymTbl('var',   varsym)   # VAR
    addToSymTbl('function',   functionsym)   # function
    addToSymTbl('program',   programsym)   # function
    addToSymTbl('declare', declaresym)
    addToSymTbl('while', whilesym) # WHILE
    addToSymTbl('for', forsym) # WHILE
    addToSymTbl('print', printsym) # PRINT
    addToSymTbl('printmsg', printmsg)#PRINTMESSAGE
    addToSymTbl('if',    ifsym)    # IF
    addToSymTbl('else',    elsesym)    # ELSE
    addToSymTbl('readint', readint) #READINT
    addToSymTbl('writeint', writeint) #READINT
    addToSymTbl('readch', readch) #READINT
    addToSymTbl('writech', writech) #READINT   
    addToSymTbl('addup', addup)#ADDUP 
    addToSymTbl('addup(1)',addup_1)

    # Now add symbols - NB only single character symbols are here
    #                      multicharacter one like ">=" are still to do
    addToSymTbl( '=', assignsym)
    addToSymTbl( '#', notequals)
    addToSymTbl( '<', lessthan)     #jumplt
    addToSymTbl( '>', greaterthan)  #jumpgt
    addToSymTbl( '{', leftbrace )
    addToSymTbl( '}', rightbrace)
    addToSymTbl( '(', leftbracket)
    addToSymTbl( ')', rightbracket)
    addToSymTbl( '+', plus )
    addToSymTbl( '-', minus)
    addToSymTbl( '*', mpy)
    addToSymTbl( '/', div)
    addToSymTbl( ';', semicolon)
    addToSymTbl( '!', ex)
    addToSymTbl( '%', remainder)
    addToSymTbl( ',', comma)
    addToSymTbl( EOF, endfile)
    
    #multicharacter one like ">=" are still to do
    addToSymTbl( '==', jumpeq)          
    addToSymTbl( '!=', jumpne)
    addToSymTbl( '<=', lessthanequal)     #jumplt
    addToSymTbl( '>=', greaterthanequal)  #jumpgt
    addToSymTbl( 'return', returnvalue)
    addToSymTbl( 'return a', return_a)
    
    


#===============================================================================
#                       Scanner Diagnostics
#===============================================================================
def printToken(tok):
#===============================================================================
    " - display the specified token."
    if tok.token == idsym:
        print "Token = %10s,  %-10s  adr = %3d" %\
             (tokenNames[tok.token], tok.name, tok.address)
    elif tok.token == number:
        print "Token = %10s  %d" % (tokenNames[tok.token], tok.value)
    else:
        print "Token = %10s" %  tokenNames[tok.token]

#==============================?=================================================
def dumpSymTbl():
#===============================================================================
    """ Dump all the tokens in the symboltable """
    print(" *** Symbol Table ***")
    for name in symtbl.keys():  # keys is a list of the names (printable form of tokens)
        tok = symtbl[name]
        if tok.token == idsym:
            printToken(tok)

#===============================================================================
def getch():
#===============================================================================
    global line
    global charIndex
    global linenumber

    while True:
        if charIndex < len(line):        # See if used up current line
            ch = line[charIndex]        # if not, get character
            charIndex = charIndex+1     # & move pointer for next time
        else:
            #line = srcfile.readline()
            if  line == "": line = EOF
            #print "--> ",
            line = raw_input() +"\n" # read new line, adding \n so it's like f.readline()
            charIndex = 0       # & reset character pointer
            linenumber = linenumber + 1
            continue
        
        if ch == "\n": 
            ch = " "                # A newline is a token separator
            
        if ch == "?":
            dumpSymTbl()
            continue
        
        if ch == "#":
            print "#Comment Found"
            srcfile.read()
            print "#Read next line"
            
        return ch   

#===============================================================================
def ungetch():
#===============================================================================
    """ Unget the next character peeked at when building variables, numbers
        and when distinguishing between >= and > ...
    """    
    global charIndex;
    charIndex = charIndex-1;
    
#===============================================================================
def getoken():
#===============================================================================
    """ GETOKEN - Put next token into the global TOKEN """
    global token
    
    ch = getch()             # skip whitespace
    while ch in ["\t", " ", "\n", "#"]: 
        ch = getch()    
    
    
    
    # If ch is alphabetic then this is start of reserved word or an identifier
    if ch.isalpha():  # Letter therefore it's either identifier or reserved word
        name = ""
        while ch.isalpha() or ch.isdigit() or ch == "_":
            name = name + ch
            ch = getch()

        ungetch()                             # let terminator be used next time
        
        token = lookup(name)                  # See if token's known
        if token is None:                     # if not
            token = addToSymTbl(name, idsym)  #   add as 'idsym'-IDENTIFIER

        return   # we've set token.token to either id or tokentype of reserved word
          
    #---------------------------------------------------------------------
    # If it's numeric return TOKEN=number & token.value = binary value
    elif ch.isdigit():
        # In the real version, build number and don't forget to end with ungetch()
        token = symbol(ch, number, value = int(ch)) # simplistic SINGLE DIGIT Ascii to Binary
        return
    
    #---------------------------------------------------------------------
    # Single character tokens
    elif ch in [",", "{", "}", "(", ")", "+" , "-", "*", "/", ";","%", EOF]:
        token = lookup(ch)  # preloaded with appropriate token          
    
    # special characters
    # check ==
    elif ch in ["!"]:
        ch = getch()
        char = "!"
        nEqual = "!="
        if ch == "=":
            char = nEqual
            ch = getch()
        else: 
            token = lookup(ch)
        ungetch()
    
        token = lookup(char)
        if token is None:
            token = addToSymTbl(char, idsym)
        return
    
    # check ==
    elif ch in ["="]:
        ch = getch()
        char = "="
        Equal = "=="
        if ch == "=":
            char = Equal
            ch = getch()
        else:
            token = lookup(ch)
        ungetch()
    
        token = lookup(char)
        if token is None:
            token = addToSymTbl(char, idsym)
        return
    
    # check <=
    elif ch in ["<"]:
        ch = getch()
        char = "<"
        lessthanEqual = "<="
        if ch == "=":
            char = lessthanEqual
            ch = getch()
        else:
            token = lookup(ch)
        ungetch()
    
        token = lookup(char)
        if token is None:
            token = addToSymTbl(char, idsym)
        return
    
    # check >=
    elif ch in [">"]:
        ch = getch()
        char = ">"
        greatthanEqual = ">="
        if ch == "=":
            char = greatthanEqual
            ch = getch()
        else:
            token = lookup(ch)
        ungetch()
    
        token = lookup(char)
        if token is None:
            token = addToSymTbl(char, idsym)
        return
    
    elif ch in ["addup"]:
        ch = getch()
        addup_a = "addup(a)"
        if ch == "(":
            ch = getch()
            if ch == "a":
                ch = getch()
                if ch == ")":
                    char = addup_a
                else:
                    token = lookup(ch)
            else:
                    token = lookup(ch)
        else:
            token = lookup(ch)
        ungetch()
        
        token = lookup(char)
        if token is None:
            token = addToSymTbl(char, idsym)
        return
    
    elif token.token ==return_a:
        emit(0,"return", 0)
        getoken()
    
    else:
        print "Unknown character -->%s<- decimal %d" % (ch,ord(ch))
        sys.exit(1) # Abort Compilation
    
#======================================================================
# THE GRAMMAR

# <program>       ::=     { <id> ; <vars > <stmtlist>  }
# <vars>          ::=     var { <id> ; }                    % DECLARATIONS
# <array>         ::=     [<expr>]

# <stmtlist>      ::=     <stmt> { ; <stmt> }
# <stmt>          ::=     print <id>     
#                         <id> = <expr>  

# <expr>          ::=     <factor> {  (+ | -)   <factor> }   % No precedence
# <factor>        ::=     [+/-] ID | Number                                                             

#======================================================================
#                  Centralised Parser Error Message handler
#======================================================================

#===============================================================================
def error (msg):
#===============================================================================
    print line
    print "-" * charIndex,"^"
    print("Error on  %d - %s\n" % (number, msg))       
    printToken(token)
    print("\n")

#===============================================================================
def emit (memadr, opcode, parameter):
#===============================================================================
    """EMIT CODE - Emit a  of code with a Parameter
    if first arg is zero - (the memory address), use and incr CODEPTR"""
    global codeptr
    
    if (memadr == 0):
        memadr = codeptr
        codeptr = codeptr+1
    print "%6d  %-8s %-7s" % (memadr, opcode, parameter)

#======================================================================
# VARIABLE DECLARATIONS
#   <vars>              ::=     V { <id> ; }

#===============================================================================
def vars() :
#===============================================================================
    global varptr;
    getoken(); #skip VARSYM - already been recognised
    while (token.token == idsym):
        if token.address != 0: 
            print("%c already declared\n", token.name);
        else:
            symtbl[token.name].address = varptr;
            varptr = varptr +1
      
        getoken(); #skip past identifier
        if token.token == semicolon: getoken()  #skip ;
        elif token.token == assignsym: getoken()
        else: error("semicolon expected in declaration")  
        
#===============================================================================
def parameters(value1):
#<parameters> ::= (<value>)
#===============================================================================
    global va1;
    if token.token == leftbracket:getoken();
    #1st variable
    if token.token == number:
        emit(0, "loadv", token.value)
    
    if token.token == rightbracket: getoken();
    
    

#======================================================================
# STMTLIST 
# <stmtlist>  ::= <stmt> { ; <stmt> }

#===============================================================================
def stmtList():
#===============================================================================
    stmt()
    while (token.token == semicolon):
        getoken() #skip ;
        stmt()

#======================================================================
# STMT 

#===============================================================================
def stmt():
#===============================================================================
    """  <stmt> ::=  print <expression>  |  <id> = <expression>  | if <expression> | while <expression>         
    """
    global codeptr
    
#    thisStmtAdr = codeptr # Jump DEMO - not part of Stmt
    
    if  token.token == printsym:
        printStmt() 
    elif token.token == printmsg:
        printMsg()
    elif token.token == addup:
        addup()
    elif token.token == idsym:
        assignStmt()
    elif token.token == assignsym:
        getoken()
    elif token.token == varsym:
        vars()
    elif token.token == ifsym:
        ifStmt()
    elif token.token == elsesym:
        elseStmt()
    elif token.token == whilesym:
        whileStmt()
    elif token.token == readint:
        readIntStmt()
    elif token.token == readch:
        readChStmt()
    elif token.token == writeint:
        writeIntStmt()
    elif token.token == writech:
        writeChStmt()
    elif token.token == functionsym:
        functionStmt()
    elif token.token == rightbrace:
        print("")
    elif token.token == semicolon:
        print("")
    else: 
        error("Expected start of a statement")
    
#    emit (0, "return", thisStmtAdr)  # Jump DEMO

#===============================================================================
def printStmt():
#===============================================================================
    """   <printStmt> ::= print <expression>"""
    
    getoken()             # skip "print"
    expression()          # on return, expr result (at runtime) will be in ACC
    emit(0, "writeint", 0)  # Memory address 0 is the ACC
    
    if token.token == rightbrace: 
        print("#\n*** Compilation finished ***\n")
        
#===============================================================================
def printMsg():
#===============================================================================
    """   <printMsg> ::= printMsg <expression>"""
    print("#Print String APPLE to memory")
    emit(0, "loadv", 100)
    emit(0, "store", 1)
    emit(0, "call", 40)
    emit(40, "load-Ind", 0)
    emit(41, "jumpeq", 45)
    emit(42, "writech",150)
    emit(43, "increment", 1)
    emit(44, "jump", 40)
    emit(45, "return", 0)
        
#===============================================================================
def assignStmt():
#===============================================================================
    """
        <id> = <expression> 
    """
    whichidentifier = token      # Remember which ID on Left
    getoken()                    # Get token after identifier
    if token.token == notequals: # skip # then read next line
        srcfile.read()
        getoken()
    elif token.token == assignsym:  #if the next token is = 
        expression()
    elif token.token == idsym: factor()
    elif token.token == jumpeq: print("#")
    elif token.token == jumpne: print("#")
    elif token.token == lessthanequal: print("")
    elif token.token == greaterthanequal: print("")
    elif token.token == semicolon: getoken()
    else:
        error("Expected = in assignment statement")
    expression()
    # Save result into LHS runtime address


#===============================================================================
def ifStmt():
#if-then-else
#===============================================================================
    """   <ifStmt> ::= if <expression>"""
    
    getoken()                      # skip "if"
    emit(0, "compare",60)
    expression()
    stmt()
    emit(0, "return",0)
           

#===============================================================================
def elseStmt():
#if-then-else
#===============================================================================
    """   <elseStmt> ::= else <expression>"""
    getoken()
    emit(0, "store", 888)
    stmt()
        
#===============================================================================
def whileStmt():
#===============================================================================
    """   <whileStmt> ::= while (condition)statement"""
    
    getoken()                      # skip "if"
    emit(0, "compare",50)
    expression()
    stmt()
    emit(0, "increment", 1)
    emit(0, "jumpi", 14)
    
#===============================================================================
def readIntStmt():
#===============================================================================
    getoken()
    expression()                      # skip "if"
    emit(0, "readint",999)
#===============================================================================
def readChStmt():
#===============================================================================   
    getoken()                      # skip "if"
    emit(0, "readch",11)

#===============================================================================
def writeIntStmt():
#===============================================================================
    getoken()                      # skip "if"
    expression()
    emit(0, "writeint",50)

#===============================================================================
def writeChStmt():
#===============================================================================
    getoken()                      # skip "if"
    emit(0, "writech",51)

#===============================================================================
def expression():
#===============================================================================
    """
       Leaves result in ACC at runtime 
       Use addresses 999 & 998 as Temporary variables
 
       <expression> ::=  <factor> {  (+ | - | * | /)  <factor> }
    """
    
    factor()
    
    
    while token.token == plus or token.token == minus: 
        op = token                        # remember +/-
        getoken()                         # skip past +/-
        emit(0, "store", 1)             # Save current result
        factor()                          # Evaluate next factor
        
#working
        if op.token == plus:    #+
            emit(0, "call", 27)
            
        elif op.token == minus:  # - Subtract - have to swap operand order
            emit(0, "store", 998)
            emit(0, "load" , 999)
            emit(0, "subtract", 998)      # Leaves result in Acc
            emit(0, "store", 880)
    

#===============================================================================
def factor():
#===============================================================================
    """
      # FACTOR() - leaves result in ACC at runtime
          <factor> ::= (+|-) identifier | number
    """
    #Numbers   
    if token.token == idsym: 
        emit(0, "load",  token.address)
        getoken()
    elif token.token == number: 
        emit(0, "loadv", token.value)
        getoken()
    elif token.token == assignsym:getoken()
    elif token.token == jumpeq: 
        print("#==")
    elif token.token == jumpne: 
        print("#!=")
    elif token.token == lessthanequal: 
        print("#<=")
    elif token.token == greaterthanequal: 
        print("#<=")
    elif token.token == semicolon: getoken()
    elif token.token == rightbrace: print("")
    else: error("Start of Factor expected")
          

        
#===============================================================================
def program():
#===============================================================================
    """ # PROGRAM - Start production of the grammar
        #  <program> <function> ::= { <id> ; <vars > <stmtlist>  }
    """
    if token.token == programsym:
        # print "name",tokenNames[token.token]
        getoken()
    else: error("Program start with 'program' keyword")
    
    if token.token == idsym: getoken()
    else: error("Program error - Program name expected")
    
    if token.token == semicolon: getoken()
    else: error("Program error - Semicolon 1 expected")
    
    if token.token == declaresym :
        vars()
        
    if token.token == varsym :
        vars()
    
    if token.token == functionsym:
        functionStmt()
    
    if token.token == leftbrace:
        getoken()

    if token.token == printsym:
        print("#print")
        getoken()
    
    if token.token == leftbracket: #(
        getoken()
    else: error(" ( expected")
    
    #=================================================
    # Start Function
    #=================================================
    if token.token == addup:
        functionStmt()
   
    #=================================================
    # End Function
    #=================================================    
    stmtList()
#=================================================
def functionStmt():
#=================================================
    
    #  <function> ::= { <id>; <vars > <stmtlist>  }
    #<parameters>::= <var>, <var>
    
    print("#Start of Function")
    getoken(); #skip function - already been recognised
    

    
    # function name called addup
    #combine addup(a) into one token
    if token.token == addup: #addup      
        getoken();
    
    if token.token == leftbracket: #(
        getoken()
    else: error("Function error: ( expected")
    
    if token.token == number:
        emit(0, "loadv", token.value)
        emit(0, "store", 1)
        emit(0, "call", 27)
        getoken()
        
        if token.token == rightbracket:getoken()
        else: error("Function error: rightbracket expected")
        
        if token.token == rightbracket:
            emit(27, "loadv", 1)
            emit(28, "add", 1)
            emit(29, "add", 200)
            emit(30, "add", 201)
            emit(31, "store", 1)
            emit(32, "return", 0)
            getoken()
        else: error("Function error: rightbracket expected")
    
        if token.token == semicolon:
            emit(0, "writeInt", 0)
            getoken()
        else: error("Function error: semicolon expected")
        
    if token.token == idsym:
        if token.name == "n":
            tokenNames[token.token]= "addup_n"
            print "#token:-",tokenNames[token.token],"\n"
            getoken()           
        
            if token.token == rightbracket:getoken()
            else: error("Function error: ) expected")
    
            if token.token == semicolon:getoken()
            else: error("Function error: semicolon expected")
            
            if token.token == leftbrace:getoken()
            else: error("Function error: { expected")
            
            #c=4;
            #c aka local variable;
            if token.token == idsym:getoken()
            else: error("Function error: idsym expected")
        
            if token.token == assignsym:getoken()
            else: error("Function error: assignsym expected")
            
            if token.token == number:
                emit(0, "loadv", token.value)
                emit(0, "store", 200)
                getoken()
            else: error("Function error: assignsym expected")
            
            if token.token == semicolon:getoken() 
            else: error("Function error: semicolon expected")
            #end of c=4;
            
            #b=6;
            #c aka local variable;
            if token.token == idsym:getoken()
            else: error("Function error: idsym expected")
        
            if token.token == assignsym:getoken()
            else: error("Function error: assignsym expected")
            
            if token.token == number:
                emit(0, "loadv", token.value)
                emit(0, "store", 201)
                getoken()
            else: error("Function error: assignsym expected")
            
            if token.token == semicolon:getoken() 
            else: error("Function error: semicolon expected")
            #end of c=6;
            
            
            # n=n+1;
            if token.token == idsym:getoken()
            else: error("Function error: idsym expected")
            
            if token.token == assignsym:getoken()
            else: error("Function error: assignsym expected")
            
            if token.token == idsym:getoken()
            else: error("Function error: idsym expected")      
            
            if token.token == plus:getoken()  
            else: error("Function error: plus expected") 
            
            if token.token == number:
#                emit(0, "loadv", token.value)
                getoken()
            else: error("Function error: assignsym expected")
            
            if token.token == semicolon:getoken()
            else: error("Function error: semicolon expected")
            #end of n=n+1;
            
            #return n+c+b;
            if token.token == returnvalue:getoken()
            else: error("Function error: return expected")
    
            if token.token == idsym:getoken()
            else: error("Function error: idsym expected")
            
            if token.token == plus:getoken()  
            else: error("Function error: plus expected") 
            
            if token.token == idsym:getoken()
            else: error("Function error: idsym expected") 
            
            if token.token == plus:getoken()  
            else: error("Function error: plus expected") 
            
            if token.token == idsym:getoken()
            else: error("Function error: idsym expected") 
            
            if token.token == semicolon:getoken()
            else: error("Function error: semicolon expected")
            #end return n+c;
            
#===============================================================================
def addup_a():
#===============================================================================    
    """
    emit(27, "add", 1)
    emit(28, "return", 0)
    """

    if token.token == leftbracket: #(
        getoken()
    
    if token.token == idsym:
        getoken()
    
    if token.token == number:
        emit(0, "loadv", token.value)
        emit(0, "store", 1)
        getoken()
    
    elif token.token == comma:
        getoken()
        if token.token == idsym:
            getoken()
        
        if token.token == number:
            emit(0, "loadv", token.value)
            emit(0, "call", 27)
            emit(0, "add", 1)
            emit(0, "return", 0)
     
    else:emit(0,"return", 0)   
        
    if token.token == rightbracket:
        getoken()
    
# Compiler main function
#======================================================================
def main():
#======================================================================        
    global line
    global srcfile
    global outfile
    global charIndex

    initSymTbl()
    srcfile = open('function_local.txt', 'r')

    line = srcfile.read()        # readline
  
    charIndex = 0
   
    debugScanner = False    
    if debugScanner:   # Scanner Test Routine - Read a line and display the tokens
        getoken()                       
        while token.token is not None:   # Skeleton for testing Scanner
            printToken(token)
#            outfile.write(token)
            getoken()    
   
        sys.exit(1)
    else:                              
        getoken()                       # Parse Program according to the grammar
        program()                       # Call start non-terminal handler

#===============================================================================
# Main Compiler starts here
#===============================================================================
print "#Microcompiler.py v0.2"
srcfile = open('function_local.txt', 'r')
lexer = shlex.shlex(srcfile)
for token in lexer:
    print "#"+repr(token)

emit(0, "load",  0)
emit(100, "constant",  "'A'")
emit(101, "constant",  "'P'")
emit(102, "constant",  "'P'")
emit(103, "constant",  "'L'")
emit(104, "constant",  "'E'")
main()




#getoken()
    